﻿namespace Fiddler
{
    using System;
    using System.IO;
    using System.Net.Sockets;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;

    internal class CONNECTTunnel : ITunnel
    {
        private long _lngEgressByteCount;
        private long _lngIngressByteCount;
        private Session _mySession;
        private byte[] arrRequestBytes;
        private byte[] arrResponseBytes;
        private bool bIsBlind;
        private bool bIsOpen = true;
        private AutoResetEvent oKeepTunnelAlive;
        private ClientPipe pipeTunnelClient;
        private ServerPipe pipeTunnelRemote;
        private Socket socketClient;
        private Socket socketRemote;

        private CONNECTTunnel(Session oSess, ClientPipe oFrom, ServerPipe oTo)
        {
            this._mySession = oSess;
            this.pipeTunnelClient = oFrom;
            this.pipeTunnelRemote = oTo;
            this._mySession.SetBitFlag(SessionFlags.IsBlindTunnel, true);
        }

        public void CloseTunnel()
        {
            try
            {
                if (this.pipeTunnelClient != null)
                {
                    this.pipeTunnelClient.End();
                }
            }
            catch (Exception)
            {
            }
            try
            {
                if (this.pipeTunnelRemote != null)
                {
                    this.pipeTunnelRemote.End();
                }
            }
            catch (Exception)
            {
            }
            try
            {
                if (this.oKeepTunnelAlive != null)
                {
                    this.oKeepTunnelAlive.Set();
                }
            }
            catch (Exception)
            {
            }
        }

        internal static void CreateTunnel(Session oSession)
        {
            if (((oSession != null) && (oSession.oRequest != null)) && (((oSession.oRequest.headers != null) && (oSession.oRequest.pipeClient != null)) && (oSession.oResponse != null)))
            {
                ClientPipe pipeClient = oSession.oRequest.pipeClient;
                if (pipeClient != null)
                {
                    oSession.oRequest.pipeClient = null;
                    ServerPipe pipeServer = oSession.oResponse.pipeServer;
                    if (pipeServer != null)
                    {
                        oSession.oResponse.pipeServer = null;
                        CONNECTTunnel tunnel = new CONNECTTunnel(oSession, pipeClient, pipeServer);
                        oSession.__oTunnel = tunnel;
                        Thread thread = new Thread(new ThreadStart(tunnel.RunTunnel));
                        thread.IsBackground = true;
                        thread.Start();
                    }
                }
            }
        }

        private void DoTunnel()
        {
            try
            {
                this.bIsBlind = !CONFIG.bMITM_HTTPS || this._mySession.oFlags.ContainsKey("x-no-decrypt");
                if (!this.bIsBlind)
                {
                    this.bIsBlind = (CONFIG.oHLSkipDecryption != null) && CONFIG.oHLSkipDecryption.ContainsHost(this._mySession.PathAndQuery);
                }
                if (!this.bIsBlind && (CONFIG.DecryptWhichProcesses != ProcessFilterCategories.All))
                {
                    string str = this._mySession.oFlags["x-ProcessInfo"];
                    if (CONFIG.DecryptWhichProcesses == ProcessFilterCategories.HideAll)
                    {
                        if (!string.IsNullOrEmpty(str))
                        {
                            this.bIsBlind = true;
                        }
                    }
                    else if (!string.IsNullOrEmpty(str))
                    {
                        bool flag = Utilities.IsBrowserProcessName(str);
                        if (((CONFIG.DecryptWhichProcesses == ProcessFilterCategories.Browsers) && !flag) || ((CONFIG.DecryptWhichProcesses == ProcessFilterCategories.NonBrowsers) && flag))
                        {
                            this.bIsBlind = true;
                        }
                    }
                }
            Label_00BC:
                this._mySession.SetBitFlag(SessionFlags.IsDecryptingTunnel, !this.bIsBlind);
                this._mySession.SetBitFlag(SessionFlags.IsBlindTunnel, this.bIsBlind);
                if (this.bIsBlind)
                {
                    this.arrRequestBytes = new byte[0x4000];
                    this.arrResponseBytes = new byte[0x4000];
                    this.socketClient = this.pipeTunnelClient.GetRawSocket();
                    this.socketRemote = this.pipeTunnelRemote.GetRawSocket();
                    this.socketClient.BeginReceive(this.arrRequestBytes, 0, this.arrRequestBytes.Length, SocketFlags.None, new AsyncCallback(this.OnClientReceive), this.socketClient);
                    this.socketRemote.BeginReceive(this.arrResponseBytes, 0, this.arrResponseBytes.Length, SocketFlags.None, new AsyncCallback(this.OnRemoteReceive), this.socketRemote);
                    this.WaitForCompletion();
                }
                else
                {
                    X509Certificate2 certificate;
                    if (CONFIG.bUseSNIForCN && !this._mySession.oFlags.ContainsKey("x-OverrideCertCN"))
                    {
                        string str2 = this._mySession.oFlags["https-Client-SNIHostname"];
                        if (!string.IsNullOrEmpty(str2) && (str2 != this._mySession.hostname))
                        {
                            this._mySession.oFlags["x-OverrideCertCN"] = this._mySession.oFlags["https-Client-SNIHostname"];
                        }
                    }
                    string sHostname = this._mySession.oFlags["x-OverrideCertCN"] ?? this._mySession.hostname;
                    try
                    {
                        certificate = CertMaker.FindCert(sHostname);
                        if (certificate == null)
                        {
                            throw new Exception("Certificate Maker returned null when asked for a certificate for " + sHostname);
                        }
                    }
                    catch (Exception exception)
                    {
                        certificate = null;
                        FiddlerApplication.Log.LogFormat("fiddler.https> Failed to obtain certificate for {0} due to {1}", new object[] { sHostname, exception.Message });
                        this._mySession.oFlags["x-HTTPS-Decryption-Error"] = "Could not find or generate interception certificate.";
                        if (FiddlerApplication.Prefs.GetBoolPref("fiddler.network.https.blindtunnelifcertunobtainable", true))
                        {
                            this.bIsBlind = true;
                            goto Label_00BC;
                        }
                    }
                    if (!this.pipeTunnelClient.SecureClientPipeDirect(certificate))
                    {
                        throw new Exception("HTTPS Handshaking to client did not succeed.");
                    }
                    if (!this.pipeTunnelRemote.SecureExistingConnection(this._mySession, sHostname, this._mySession.oFlags["https-Client-Certificate"], string.Concat(new object[] { this.pipeTunnelRemote.isConnectedToGateway ? "GATEWAY:" : string.Empty, "HTTPS:", this._mySession.hostname, ":", this._mySession.port }), ref this._mySession.Timers.HTTPSHandshakeTime))
                    {
                        throw new Exception("HTTPS Handshaking to server did not succeed.");
                    }
                    this._mySession.responseBodyBytes = Encoding.UTF8.GetBytes("Encrypted HTTPS traffic flows through this CONNECT tunnel. HTTPS Decryption is enabled in Fiddler, so decrypted sessions running in this tunnel will be shown in the Web Sessions list.\n\n" + this.pipeTunnelRemote.DescribeConnectionSecurity());
                    Session session = new Session(this.pipeTunnelClient, this.pipeTunnelRemote);
                    session.oFlags["x-serversocket"] = this._mySession.oFlags["x-securepipe"];
                    if ((this.pipeTunnelRemote != null) && (this.pipeTunnelRemote.Address != null))
                    {
                        session.m_hostIP = this.pipeTunnelRemote.Address.ToString();
                        session.oFlags["x-hostIP"] = session.m_hostIP;
                        session.oFlags["x-EgressPort"] = this.pipeTunnelRemote.LocalPort.ToString();
                    }
                    session.Execute(null);
                }
            }
            catch (Exception)
            {
                try
                {
                    this.pipeTunnelClient.End();
                    this.pipeTunnelRemote.End();
                }
                catch (Exception)
                {
                }
            }
        }

        protected void OnClientReceive(IAsyncResult ar)
        {
            try
            {
                int count = this.socketClient.EndReceive(ar);
                if (count > 0)
                {
                    this._lngEgressByteCount += count;
                    if ((this._mySession.requestBodyBytes == null) || (this._mySession.requestBodyBytes.LongLength == 0))
                    {
                        try
                        {
                            HTTPSClientHello hello = new HTTPSClientHello();
                            if (hello.LoadFromStream(new MemoryStream(this.arrRequestBytes, 0, count, false)))
                            {
                                this._mySession.requestBodyBytes = Encoding.UTF8.GetBytes(hello.ToString() + "\n");
                                this._mySession["https-Client-SessionID"] = hello.SessionID;
                                if (!string.IsNullOrEmpty(hello.ServerNameIndicator))
                                {
                                    this._mySession["https-Client-SNIHostname"] = hello.ServerNameIndicator;
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            this._mySession.requestBodyBytes = Encoding.UTF8.GetBytes("Request HTTPSParse failed: " + exception.Message);
                        }
                    }
                    this.socketRemote.BeginSend(this.arrRequestBytes, 0, count, SocketFlags.None, new AsyncCallback(this.OnRemoteSent), this.socketRemote);
                }
                else
                {
                    this.CloseTunnel();
                }
            }
            catch (Exception)
            {
                this.CloseTunnel();
            }
        }

        protected void OnClientSent(IAsyncResult ar)
        {
            try
            {
                if (this.socketClient.EndSend(ar) > 0)
                {
                    this.socketRemote.BeginReceive(this.arrResponseBytes, 0, this.arrResponseBytes.Length, SocketFlags.None, new AsyncCallback(this.OnRemoteReceive), this.socketRemote);
                }
            }
            catch (Exception)
            {
            }
        }

        protected void OnRemoteReceive(IAsyncResult ar)
        {
            try
            {
                int count = this.socketRemote.EndReceive(ar);
                if (count > 0)
                {
                    this._lngIngressByteCount += count;
                    if ((this._mySession.responseBodyBytes == null) || (this._mySession.responseBodyBytes.LongLength == 0))
                    {
                        try
                        {
                            HTTPSServerHello hello = new HTTPSServerHello();
                            if (hello.LoadFromStream(new MemoryStream(this.arrResponseBytes, 0, count, false)))
                            {
                                string s = string.Format("This is a CONNECT tunnel, through which encrypted HTTPS traffic flows.\n{0}\n\n{1}\n", CONFIG.bMITM_HTTPS ? "Fiddler's HTTPS Decryption feature is enabled, but this specific tunnel was configured not to be decrypted. Settings can be found inside Tools > Fiddler Options > HTTPS." : "To view the encrypted sessions inside this tunnel, enable the Tools > Fiddler Options > HTTPS > Decrypt HTTPS traffic option.", hello.ToString());
                                this._mySession.responseBodyBytes = Encoding.UTF8.GetBytes(s);
                                this._mySession["https-Server-SessionID"] = hello.SessionID;
                                if (hello.bNPNToSPDY)
                                {
                                    this._mySession.ViewItem.ImageIndex = 0x26;
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            this._mySession.requestBodyBytes = Encoding.UTF8.GetBytes("Response HTTPSParse failed: " + exception.Message);
                        }
                    }
                    this.socketClient.BeginSend(this.arrResponseBytes, 0, count, SocketFlags.None, new AsyncCallback(this.OnClientSent), this.socketClient);
                }
                else
                {
                    this.CloseTunnel();
                }
            }
            catch (Exception)
            {
                this.CloseTunnel();
            }
        }

        protected void OnRemoteSent(IAsyncResult ar)
        {
            try
            {
                if (this.socketRemote.EndSend(ar) > 0)
                {
                    this.socketClient.BeginReceive(this.arrRequestBytes, 0, this.arrRequestBytes.Length, SocketFlags.None, new AsyncCallback(this.OnClientReceive), this.socketClient);
                }
            }
            catch (Exception)
            {
            }
        }

        private void RunTunnel()
        {
            if (FiddlerApplication.oProxy != null)
            {
                try
                {
                    this.DoTunnel();
                }
                catch (Exception exception)
                {
                    FiddlerApplication.ReportException(exception, "Uncaught Exception in Tunnel; Session #" + this._mySession.id.ToString());
                }
            }
        }

        private void WaitForCompletion()
        {
            AutoResetEvent oKeepTunnelAlive = this.oKeepTunnelAlive;
            this.oKeepTunnelAlive = new AutoResetEvent(false);
            this.oKeepTunnelAlive.WaitOne();
            this.oKeepTunnelAlive.Close();
            this.oKeepTunnelAlive = null;
            this.bIsOpen = false;
            this.arrRequestBytes = (byte[]) (this.arrResponseBytes = null);
            this.pipeTunnelClient = null;
            this.pipeTunnelRemote = null;
            this.socketClient = (Socket) (this.socketRemote = null);
            if ((this._mySession.oResponse != null) && (this._mySession.oResponse.headers != null))
            {
                this._mySession.oResponse.headers["EndTime"] = DateTime.Now.ToString("HH:mm:ss.fff");
                this._mySession.oResponse.headers["ClientToServerBytes"] = this._lngEgressByteCount.ToString();
                this._mySession.oResponse.headers["ServerToClientBytes"] = this._lngIngressByteCount.ToString();
            }
            this._mySession.Timers.ServerDoneResponse = this._mySession.Timers.ClientBeginResponse = this._mySession.Timers.ClientDoneResponse = DateTime.Now;
            this._mySession = null;
        }

        public long EgressByteCount
        {
            get
            {
                return this._lngEgressByteCount;
            }
        }

        public long IngressByteCount
        {
            get
            {
                return this._lngIngressByteCount;
            }
        }

        public bool IsOpen
        {
            get
            {
                return this.bIsOpen;
            }
        }
    }
}

